﻿Namespace Util.SQL

    Enum tipoClausula
        tWhere = 1  'Tipo Where Comum
        tAnd        'Tipo AND
        tOr         'Tipo OR
        tComp       'Tipo Composto para passar mais de uma clausula 
        tJoin       'Tipo Join (Inner Join)
        tGroup      'Tipo GroupBy
        tOrderBy    'Tipo OrderBy
    End Enum

    Public Enum tipoAdicao
        GroupBy = 1
        OrderBy
    End Enum

    <Serializable()> _
    Structure Clausula
        Public Tipo As tipoClausula
        Public Condicao As String
        Public Tabela As String
        Public Campos As LinkedList(Of String)

        Public Function Clone() As Clausula
            Return CType(Me.MemberwiseClone, Clausula)
        End Function
    End Structure

    <Serializable()> _
    Structure AAdicionar
        Public tipo As tipoAdicao
        Public campos As LinkedList(Of String)
    End Structure

    <Serializable()> _
    Public Class Query
        Private Const BD As String = "ORACLE8"

        Private filha As Query = Nothing
        Private raiz As Query = Nothing
        Private tabela As String = ""
        Private clausula As Clausula = Nothing
        Private whereusado As Boolean = False
        Private _campos As LinkedList(Of String) = Nothing
        Private adicoes As LinkedList(Of AAdicionar) = New LinkedList(Of AAdicionar)


#Region "CONSTRUTORES"
        ''' <summary>
        ''' Cria uma nova query para a tabela específicada
        ''' </summary>
        ''' <param name="tabela">Nome da tabela como está no banco</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal tabela As String)
            Me.tabela = tabela
        End Sub

        ''' <summary>
        ''' Cria uma nova query que busca os _campos listados em _campos
        ''' </summary>
        ''' <param name="tabela">Nome da tabela no banco de dados</param>
        ''' <param name="campos">Lista de string contendo os nomes dos _campos que deseja buscar</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal tabela As String, ByVal campos As LinkedList(Of String))
            Me.tabela = tabela
            Me.Campos = campos
        End Sub

        ''' <summary>
        ''' Metodo interno para ser chamado nas clausulas
        ''' </summary>
        ''' <remarks></remarks>
        Private Sub New()
        End Sub

        Public Property Campos() As LinkedList(Of String)
            Get
                Return _campos
            End Get
            Set(ByVal value As LinkedList(Of String))
                _campos = value
            End Set
        End Property

#End Region

#Region "METODOS FUNCIONAIS"
        ''' <summary>
        ''' Objeto Interno que representa uma clausula de processamento SQL
        ''' </summary>
        ''' <param name="condicao">Uma condição específica</param>
        ''' <param name="tipoCond">Tipo da condição</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function Clausule(ByVal condicao As String, ByVal tipoCond As tipoClausula) As Query
            Dim _query As New Query

            Dim _clausula As New Clausula
            _clausula.Tipo = tipoCond
            _clausula.Condicao = condicao

            _query.clausula = _clausula
            If raiz IsNot Nothing Then
                _query.raiz = raiz
            Else
                _query.raiz = Me
            End If
            filha = _query
            Return _query

        End Function

        ''' <summary>
        ''' Indica uma clausula "Onde" que apesar do SQL pode ser utilizada várias vezes.
        ''' Se for ultilizada mais de uma vez é tratada como se fosse um AND
        ''' </summary>
        ''' <param name="condicao">Valor da condição ex: "Id = 2"</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Where(ByVal condicao As String) As Query
            Return Clausule(condicao, tipoClausula.tWhere)
        End Function

        ''' <summary>
        ''' Clausula que represenda um "E" no Banco 
        ''' </summary>
        ''' <param name="condicao">Valor da condição</param>
        ''' <returns>Nova Query contendo os valores processados</returns>
        ''' <remarks></remarks>
        Public Function tAnd(ByVal condicao As String) As Query
            Return Clausule(condicao, tipoClausula.tAnd)
        End Function

        ''' <summary>
        ''' Clausula que represenda um "OU" no Banco 
        ''' </summary>
        ''' <param name="condicao">Valor da condição</param>
        ''' <returns>Nova Query contendo os valores processados</returns>
        ''' <remarks></remarks>
        Public Function tOr(ByVal condicao As String) As Query
            Return Clausule(condicao, tipoClausula.tOr)
        End Function

        ''' <summary>
        ''' Representa um join entre duas tabelas Deve ser o primeiro a ser utilizado
        ''' </summary>
        ''' <param name="tabela">Nome da tabela no banco para dar join</param>
        ''' <param name="condicao">Campo para dar join e condições</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Join(ByVal tabela As String, ByVal condicao As String) As Query
            Dim _query As New Query

            Dim _clausula As New Clausula
            _clausula.Tabela = tabela
            _clausula.Condicao = condicao
            _clausula.Tipo = tipoClausula.tJoin

            _query.clausula = _clausula

            If raiz IsNot Nothing And Me.clausula.Tipo <> tipoClausula.tJoin Then
                Throw New Exception("Os joins devem ser os primeiros condicionais a ser utilizados.")
            Else
                If raiz IsNot Nothing Then
                    _query.raiz = raiz
                Else
                    _query.raiz = Me
                End If
            End If
            filha = _query
            Return _query

        End Function

        ''' <summary>
        ''' Agrupa seguindo a ordem dos _campos listados
        ''' </summary>
        ''' <param name="campos">_campos para agrupamento em ordem</param>
        ''' <returns>Objeto Query</returns>
        ''' <remarks></remarks>
        Public Function GroupBy(ByVal campos As LinkedList(Of String)) As Query

            If filha IsNot Nothing Or clausula.Tipo = tipoClausula.tGroup Then
                Throw New Exception("O group tem que ser o utlimo a ser utilizado.")
            End If

            Dim _query As New Query

            Dim _clausula As New Clausula
            _clausula.Tipo = tipoClausula.tGroup

            _clausula.Campos = campos

            _query.clausula = _clausula

            _query.raiz = raiz

            filha = _query
            Return _query

        End Function

        ''' <summary>
        ''' Clausula que represenda um "OU" no Banco 
        ''' </summary>
        ''' <param name="campos">_campos para ordernar na ordem de prioridade</param>
        ''' <returns>Nova Query contendo os valores processados</returns>
        ''' <remarks></remarks>
        Public Function OrderBy(ByVal campos As LinkedList(Of String)) As Query
            If filha IsNot Nothing Or clausula.Tipo = tipoClausula.tOrderBy Then
                Throw New Exception("O OrderBy tem que ser o utlimo a ser utilizado.")
            End If

            Dim _query As New Query

            Dim _clausula As New Clausula
            _clausula.Tipo = tipoClausula.tOrderBy

            _clausula.Campos = campos

            _query.clausula = _clausula

            _query.raiz = raiz

            filha = _query
            Return _query
        End Function

        ''' <summary>
        ''' Marca operações para serem adicionadas log antes da execução
        ''' </summary>
        ''' <param name="campos">_campos para serem adicionados</param>
        ''' <param name="tipo">Tipo Pode Ser GroupBy ou OrderBy</param>
        ''' <remarks></remarks>
        Public Sub AdicionarAoExecutar(ByVal campos As LinkedList(Of String), ByVal tipo As tipoAdicao)
            Dim adicao As New AAdicionar
            adicao.campos = campos
            adicao.tipo = tipo
            If raiz Is Nothing Then
                adicoes.AddLast(adicao)
            Else
                raiz.adicoes.AddLast(adicao)
            End If

        End Sub

#End Region

#Region "Renderização"
        ''' <summary>
        ''' Retorna o valor de query esperado para a raiz
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function valorRaiz()
            Dim _query As String = "Select "
            If Campos Is Nothing Or Campos.Count = 0 Then
                _query = _query & " * "
            Else
                Dim _primeiro As Boolean = True
                For Each item In Campos
                    If _primeiro Then
                        _primeiro = False
                    Else
                        _query = _query & ", "
                    End If

                    _query = _query & item
                Next
                'q = q & ")"
            End If

            _query = _query & " FROM " & tabela
            If whereusado = False Then _query = _query & " WHERE 1=1 " 'Adicionado para os Wheres virarem ANDs
            Return _query
        End Function

        ''' <summary>
        ''' Retorna o valor da condição existente
        ''' </summary>
        ''' <returns>String representando o valor da condição sendo processada no momento</returns>
        ''' <remarks></remarks>
        Private Function valorWhere() As String

            If clausula.Tipo <> tipoClausula.tWhere And clausula.Tipo <> tipoClausula.tAnd Then _
                Throw New Exception("Esse método não pode ser chamado se a condição não é where")

            Dim _queryString As String = ""
            _queryString = " AND "

            _queryString += clausula.Condicao

            Return _queryString
        End Function

        ''' <summary>
        ''' Retorna o valor da condição existente
        ''' </summary>
        ''' <returns>String representando o valor da condição sendo processada no momento</returns>
        ''' <remarks></remarks>
        Private Function valorOR() As String

            If clausula.Tipo <> tipoClausula.tOr Then _
                Throw New Exception("Esse método não pode ser chamado se a condição não é OR")

            Dim _queryString As String = ""
            _queryString = " OR "

            _queryString += clausula.Condicao

            Return _queryString
        End Function

        ''' <summary>
        ''' Retorna o valor do join existente
        ''' </summary>
        ''' <returns>String representando o valor da condição sendo processada no momento</returns>
        ''' <remarks></remarks>
        Private Function valorJoin(ByRef raiz As Query, ByRef rqry As String) As String
            If clausula.Tipo <> tipoClausula.tJoin Then _
                Throw New Exception("Esse método não pode ser chamado se não é um join")

            Dim _queryString As String = ""

            If BD <> "ORACLE8" Then
                _queryString = " Join " & clausula.Tabela & " ON " & clausula.Condicao

                If raiz.whereusado = False Then
                    raiz.whereusado = True
                    _queryString = _queryString & " WHERE 1=1 " 'Adicionado para os Wheres virarem ANDs
                End If

            Else
                'Método feito porque o Oracle8i não suporta o JOIN ANSI 

                _queryString = " , " & clausula.Tabela & " "

                If raiz.whereusado = False Then
                    raiz.whereusado = True
                    _queryString = _queryString & " WHERE " & clausula.Condicao & " "  'Adicionado para os Wheres virarem ANDs
                Else
                    Dim i = rqry.IndexOf("WHERE")
                    rqry = rqry.Insert(i + 5, " " & clausula.Condicao & " AND ")

                End If

            End If

            Return _queryString
        End Function

        ''' <summary>
        ''' Retorna o valor do GroupBy existente
        ''' </summary>
        ''' <returns>String representando o valor da condição sendo processada no momento</returns>
        ''' <remarks></remarks>
        Private Function valorGroupBy() As String
            If clausula.Tipo <> tipoClausula.tGroup Then _
                Throw New Exception("Esse método não pode ser chamado se não é um Group")

            Dim _queryString As String = ""

            _queryString = " Group By "
            Dim primeiro As Boolean = True
            For Each item In clausula.Campos
                If Not primeiro Then
                    _queryString = _queryString & ", "
                End If
                _queryString = _queryString & item
                If primeiro Then
                    primeiro = False
                End If
            Next


            Return _queryString
        End Function

        ''' <summary>
        ''' Retorna o valor do OrderBy existente
        ''' </summary>
        ''' <returns>String representando o valor da condição sendo processada no momento</returns>
        ''' <remarks></remarks>
        Private Function valorOrderBy() As String
            If clausula.Tipo <> tipoClausula.tOrderBy Then _
                Throw New Exception("Esse método não pode ser chamado se não é um OrderBy")

            Dim _queryString As String = ""

            _queryString = " Order By "
            Dim primeiro As Boolean = True
            For Each item In clausula.Campos
                If Not primeiro Then
                    _queryString = _queryString & ", "
                End If
                _queryString = _queryString & item
                If primeiro Then
                    primeiro = False
                End If
            Next


            Return _queryString
        End Function

        ''' <summary>
        ''' Executa para todas as filhas
        ''' </summary>
        ''' <param name="_query">Query Q</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Private Function ExecutarFilhas(ByVal _query As Query) As String
            Dim _queryString As String = ""

            If _query.filha IsNot Nothing Then
                _queryString = ExecutarFilhas(_query.filha) & _queryString
            End If

            If _query.raiz Is Nothing Then
                _queryString = _query.valorRaiz & _queryString
            End If

            If _query.clausula.Tipo = tipoClausula.tWhere Or _query.clausula.Tipo = tipoClausula.tAnd Then
                _queryString = _query.valorWhere & _queryString
            End If

            If _query.clausula.Tipo = tipoClausula.tOr Then
                _queryString = _query.valorOR & _queryString
            End If

            If _query.clausula.Tipo = tipoClausula.tJoin Then
                _queryString = _query.valorJoin(raiz, _queryString) & _queryString
            End If

            If _query.clausula.Tipo = tipoClausula.tGroup Then
                _queryString = _query.valorGroupBy & _queryString
            End If

            If _query.clausula.Tipo = tipoClausula.tOrderBy Then
                _queryString = _query.valorOrderBy & _queryString
            End If

            Return _queryString
        End Function

        ''' <summary>
        ''' Monta a query string desta query
        ''' </summary>
        ''' <returns>String representando a queryString</returns>
        ''' <remarks></remarks>
        Public Function Executar() As String
            Dim _query As Query = Nothing
            'Executando as adições de ultima hora
            Dim _adicoes As LinkedList(Of AAdicionar)

            If raiz Is Nothing Then
                _adicoes = Me.adicoes
            Else
                _adicoes = raiz.adicoes
            End If

            For Each adicao In _adicoes
                Select Case (adicao.tipo)
                    Case tipoAdicao.GroupBy
                        _query = Me.GroupBy(adicao.campos)
                        Exit Select
                    Case tipoAdicao.OrderBy
                        _query = Me.OrderBy(adicao.campos)
                        Exit Select
                End Select
            Next

            If _query Is Nothing Then
                _query = Me
            End If

            _query.whereusado = False

            If _query.raiz Is Nothing Then
                'Throw New Exception("Impossível gerar uma query sem condições")
                Return _query.ExecutarFilhas(_query)
            Else
                _query.raiz.whereusado = False
            End If


            Return _query.ExecutarFilhas(raiz)

        End Function
#End Region

#Region "METODOS AUXILIARES"

        ''' <summary>
        ''' Cria uma cópia do objeto query para alterações.
        ''' </summary>
        ''' <returns>Cópia do mesmo objeto</returns>
        ''' <remarks></remarks>
        Public Function Clone() As Query
            '-- Não funciona porque é composto por objetos (Reference Type)
            '-- O método abaixo só clona os atributos Value Type
            'Return CType(Me.MemberwiseClone(), Query)

            '-- A clonagem abaixo funciona se o objeto for serializable
            Dim _MemoStream As New IO.MemoryStream
            Dim _BinaryFormat As New Runtime.Serialization.Formatters.Binary.BinaryFormatter
            _BinaryFormat.Serialize(_MemoStream, Me)
            _MemoStream.Position = 0
            Return _BinaryFormat.Deserialize(_MemoStream)

        End Function

#End Region

    End Class
End Namespace

